package 数字图像处理.filter;

import jdk.nashorn.internal.ir.LiteralNode;
import org.junit.Test;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

/**
 * @author Summerday
 */
public class WaveFilter {

    private final int[][] matrix;

    private final int row;
    private final int col;
    /**
     * 均值滤波系数
     */
    private final int[][] MeanMatrix = {
            {1, 1, 1},
            {1, 1, 1},
            {1, 1, 1}
    };

    public WaveFilter(int[][] matrix) {
        this.matrix = matrix;
        this.row = matrix.length;
        this.col = matrix[0].length;
    }

    /**
     * 根据元素,获取周围数的数列,默认为3*3
     *
     * @param i 元素的索引行i
     * @param j 元素的索引列j
     * @return 返回3*3的二维数组
     */
    private int[][] getAroundByElement(int i, int j) {
        return getAroundByElement(i, j, 1, 1, 3);
    }
    private int[][] getAroundByElement(int i, int j,int lRange,int rRange,int size) {
        int[][] temp = new int[size][size];

        for (int k = i - lRange, m = 0; k <= i + lRange; k++, m++) {
            for (int l = j - rRange, n = 0; l <= j + rRange; l++, n++) {
                temp[m][n] = matrix[k][l];
            }
        }
        return temp;

    }

    /**
     * 两个矩阵的卷积和,假设matrix1和2大小相等
     *
     * @param matrix1 二维数组1
     * @param matrix2 二维数组2
     * @return 卷积和
     */
    private int convolution(int[][] matrix1, int[][] matrix2) {
        int sum = 0;
        for (int i = 0; i < matrix1.length; i++) {
            for (int j = 0; j < matrix1[0].length; j++) {
                sum += matrix1[i][j] * matrix2[i][j];
            }
        }
        return sum;
    }

    /**
     * 普通均值滤波
     */
    public int[][] meanFilter() {
        int[][] curr = new int[row][col];
        for (int i = 0; i < row; i++) {
            if (col >= 0) System.arraycopy(matrix[i], 0, curr[i], 0, col);
        }

        for (int i = 1; i < row - 1; i++) {
            for (int j = 1; j < col - 1; j++) {
                int[][] temp = getAroundByElement(i, j);
                double t = 0;
                double result = t+convolution(temp, MeanMatrix);

                curr[i][j] = (int) Math.round(result / 9);
            }
        }
        return curr;
    }

    /**
     * 将二维数组的数添加到list返回
     *
     * @param matrix 二维数组
     * @return list
     */
    private List<Integer> getList(int[][] matrix) {
        //新建list存储二维数组
        List<Integer> list = new ArrayList<>();
        for (int[] ints : matrix) {
            for (int j = 0; j < matrix[0].length; j++) {
                list.add(ints[j]);
            }
        }
        return list;
    }

    /**
     * 将matrix转化为有序的列表
     *
     * @param matrix 二维数组
     * @return 有序的列表
     */
    private List<Integer> getSortList(int[][] matrix) {
        //将获取的
        List<Integer> list = getList(matrix);
        Collections.sort(list);
        return list;
    }

    /**
     * 中值滤波器
     *
     * @return 滤波后的数组
     */
    public int[][] medianFilter() {
        int[][] curr = new int[row][col];
        for (int i = 0; i < row; i++) {
            if (col >= 0) System.arraycopy(matrix[i], 0, curr[i], 0, col);
        }
        for (int i = 1; i < row - 1; i++) {
            for (int j = 1; j < col - 1; j++) {
                //获取周围3*3的模板
                int[][] temp = getAroundByElement(i, j);
                //将模板内的数放入list并排序
                List<Integer> sortList = getSortList(temp);
                //找到中值
                int result = sortList.get(sortList.size() / 2);
                curr[i][j] = result;
            }
        }
        return curr;

    }

    /**
     * 获取k近邻list
     * @param list 传入的3*3模板
     * @param target 中间值
     * @param k k
     * @return k近邻list
     */
    private List<Integer> getNearKnnElement(List<Integer> list, int target, int k) {
        //将list同时减去target
        for (int i = 0; i < list.size(); i++) {
            list.set(i,list.get(i)-target);
        }
        //将得到的列表进行排序,绝对值小代表更接近target
        list.sort(new Comparator<Integer>() {
            @Override
            public int compare(Integer o1, Integer o2) {
                return Math.abs(o1) - Math.abs(o2);
            }
        });
        List<Integer> result = new ArrayList<>();

        for(int i = 0;i<k;i++){
            result.add(list.get(i)+target);
        }

        return result;

    }

    /**
     * 求list的平均值
     * @param list 需要计算的list
     * @return list的平均值
     */
    private int getAverage(List<Integer> list){
        double sum = 0;
        for (Integer integer : list) {
            sum+=integer;
        }
        return (int) Math.round(sum/list.size());
    }

    /**
     * k近邻均值滤波,默认为3*3的模板
     *
     * 在模板中，选择K个与待处理像素的灰度差为最小的像素
     *
     * 将这K个像素的灰度均值替换掉原来的像素值
     * @param k k
     * @return k近邻均值滤波后的二维矩阵
     */
    public int[][] knnFilter(int k) {
        System.out.println("k近邻均值滤波器 k = "+k);
        int[][] curr = new int[row][col];
        for (int i = 0; i < row; i++) {
            if (col >= 0) System.arraycopy(matrix[i], 0, curr[i], 0, col);
        }
        for (int i = 1; i < row - 1; i++) {
            for (int j = 1; j < col - 1; j++) {
                int[][] temp = getAroundByElement(i, j);
                //size == 4 的list
                List<Integer> kList = getNearKnnElement(getList(temp), matrix[i][j], k);
                //求klist的平均值
                curr[i][j] = getAverage(kList);
            }
        }
        return curr;
    }

    /**
     * 对称近邻滤波
     * 从模板中的对称点对寻找与待处理像素相同区域的点。然后对选出的点做均值运算。
     * @return 对称近邻滤波后的灰度
     */
    public int[][] symmetricFilter(){
        int[][] curr = new int[row][col];
        for (int i = 0; i < row; i++) {
            if (col >= 0) System.arraycopy(matrix[i], 0, curr[i], 0, col);
        }
        for (int i = 1; i < row - 1; i++) {
            for (int j = 1; j < col - 1; j++) {
                int[][] temp = getAroundByElement(i, j);
                List<Integer> symmetricList = getSymmetricList(getList(temp));
                //求symmetricList的平均值
                curr[i][j] = getAverage(symmetricList);
            }
        }
        return curr;
    }

    /**
     * 将传入的list列表转化为两个对称列表,选取其中较小的,默认3*3的模板
     * @param temp [a1,d2,c2,b1,target,b2,c1,d1,a2]
     * @return 比较之后较小的数形成的列表
     */
    private List<Integer> getSymmetricList(List<Integer> temp){
        //存放对称对中较接近数的数列
        List<Integer> result = new ArrayList<>();
        int tempSize = temp.size();
        //中间的数
        int target = temp.get(tempSize/2);
        //以中间的数为转折
        for(int i = 0;i<tempSize/2;i++){
            //从前向后的差值
            int dif1 = temp.get(i)-target;
            //从后向前的差值
            int dif2 = temp.get(tempSize-1-i)-target;
            if(Math.abs(dif1)<=Math.abs(dif2)){
                result.add(temp.get(i));
            }else {
                result.add(temp.get(tempSize-1-i));
            }
        }
        return result;
    }

    /**
     * sigma平滑滤波,[f(i,j)-2σ, f(i,j)+2σ]
     * @return sigma滤波后的结果
     */
    public int[][] sigmaFilter(){
        int[][] curr = new int[row][col];
        for (int i = 0; i < row; i++) {
            if (col >= 0) System.arraycopy(matrix[i], 0, curr[i], 0, col);
        }
        for (int i = 1; i < row - 1; i++) {
            for (int j = 1; j < col - 1; j++) {
                int[][] temp = getAroundByElement(i, j);
                List<Integer> list = getList(temp);
                //获取标准差
                double v = getStandardDeviation(list);
                double low = matrix[i][j] - 2*v;
                double high = matrix[i][j] + 2*v;

                List<Integer> sigmaList = getSigmaList(list, low, high);
                //求symmetricList的平均值
                curr[i][j] = getAverage(sigmaList);
            }
        }
        return curr;
    }


    /**
     * 根据传入的范围筛选list
     * @param list list
     * @return 筛选后的list
     */
    private List<Integer> getSigmaList(List<Integer> list,double low,double high) {
        List<Integer> result = new ArrayList<>();
        for (int item : list) {
            if(item>=low&&item<=high){
                result.add(item);
            }
        }
        return result;
    }

    /**
     * 计算方差
     * @param list list
     * @return list数的方差
     */
    private double getVariance(List<Integer> list){
        //获取平均值
        int average = getAverage(list);
        //获取个数
        int size = list.size();

        double dVar = 0;
        for (int item : list) {
            dVar += (item - average) * (item - average);
        }
        return dVar/size;
    }

    /**
     * 计算标准差
     */
    private double getStandardDeviation(List<Integer> list){
        //获取方差
        double variance = getVariance(list);
        return Math.sqrt(variance);
    }
    /**
     * 打印显示
     *
     * @param matrix 打印的二维数组
     */
    public void list(int[][] matrix) {
        for (int[] ints : matrix) {
            for (int j = 0; j < matrix[0].length; j++) {
                System.out.print(ints[j] + " ");
            }
            System.out.println();
        }
    }
}
